/*
 *  Copyright 2012 Iurii Dunko, Iana Potochniak.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.yyminijava.codegeneration;

import org.opu.yyminijava.util.CodeExecuteException;
import org.opu.yyminijava.util.CodeGenerationException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opu.yyminijava.syntaxtree.FormalParameter;
import org.opu.yyminijava.syntaxtree.FormalParameterList;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.ElementMap;
import org.simpleframework.xml.Root;

/**
 *
 * @author Iurii Dunko
 * @author Iana Potochniak
 */
@Root
public class Method extends Printable implements Serializable{
	
	private static final long serialVersionUID = -9010275170388741004L;

	/** Default value for variables */
    public static final int DEFAULT_VALUE = 0;

    /**
     * The all variables is integer type
     * and we can't have variable with the same names,
     * also each variable must have value.
     * So, it's better to organize as a map.
     */
    @ElementMap
    private HashMap<String /*var name*/, Integer /*var value*/> variables
            // hashmap will ensure the uniqueness of variables
            = new HashMap<String, Integer>();

    /** 
     * Create additional map for navigation of the variables by it id
     */
    @ElementMap
    private HashMap<Integer, String> variablesIndexes
            = new HashMap<Integer, String>();

    /** 
     * The set of action that need to do in this method
     */
    @ElementList
    private ArrayList<Instruction> instructions = new ArrayList<Instruction>();

    @Attribute
    private String name;

    @Attribute
    private boolean staticModifier = false; 

    public Method(String name) {
        this.name = name;
    }
    
    /**
     * @deprecated this constructor must be used only for serialization
     */
    public Method() {
	}

	public String getName() {
        return name;
    }

    /**
     * @deprecated this method must be used only for serialization
     */
    public void setName(String name) {
        this.name = name ;
    }

    /**
     * This method create unique variables in variables set with default values.
     * And add instruction {@link ICode#ISTORE} to init it value when this 
     * method will be called.
     * It must be called when we build bytecode (not execute).
     * 
     * @param parameterList variable declaration
     */
    public void initVariables(FormalParameterList parameterList){
        if(!variables.isEmpty()){
            throw new CodeGenerationException("Method " + this.name + " already"
                    + " containe variables " + variables);
        }

        if (parameterList != null){
            List<FormalParameter> parameters = parameterList.getParameters();
            if (parameters != null && !parameters.isEmpty()){
                for (FormalParameter param : parameters) {
                    int num = addVariable(param.getSimpleName());
                    addInstruction(ICode.ISTORE, num);
                }
            }
        }
    }

    public void initVariablesValues(int... val){
        for(int i = 0; i < val.length; i++){
            setVariableValue(i, val[i]);
        }
    }

    /**
     * Added new variables with default value to the set of all variables.
     *
     * @param name the <b>unique</b> name of new variable
     * @return index of new variable
     * @throws CodeGenerationException fire if variable with the same name is
     * already defined
     * @see #DEFAULT_VALUE
     */
    public int addVariable(String name) throws CodeGenerationException {
        /*
         * Simple types must have default value.
         * Let's do like in 'C' - '0' is default value.
         * For boolean 0 is 'false'.
         */
        return addVariable(name, DEFAULT_VALUE);
    }

    /**
     * Added new variables to the set of all variables.
     * 
     * @param name the <b>unique</b> name of new variable
     * @param value the variable value
     * @return index of new variable
     * @throws CodeGenerationException fire if variable with the same name is
     * already defined
     */
    public int addVariable(String name, int value) throws CodeGenerationException {
        if (variables.containsKey(name)){
            throw new CodeGenerationException("Method " + this.name + " already"
                    + " have variable " + name);
        }

        variables.put(name, value);
        
        int size = variablesIndexes.size();
        variablesIndexes.put(size, name);
        return size;
    }

    /**
     * Return variable value on specific index.
     * 
     * @param index index of variable in variable table
     * @return variable value on specific index.
     */
    public int getVariable(int index){
        return getVariable(variablesIndexes.get(index));
    }

    /**
     * Return variable value with specific name.
     * 
     * @param name the name of the variable.
     * @return variable value with specific name.
     */
    public int getVariable(String name){
        if (variables.containsKey(name)){
            return variables.get(name);
        } else {
            throw new CodeExecuteException("Method " + this.name + " don't"
                    + " containe variable " + name);
        }
    }
    
    /**
     * Return index of variable with specific name.
     * 
     * @param name the name of the variable.
     * @return index of variable with specific name.
     */
    public int indexOf(String name){
    	for (int i: variablesIndexes.keySet()){
    		if (name.equals(variablesIndexes.get(i))){
    			return i;
    		}
    	}
    	
    	throw new CodeExecuteException("Method " + this.name + " don't"
                + " containe variable " + name);
    }

    public void setVariableValue(int index, int value){
        setVariableValue(variablesIndexes.get(index), value);
    }

    public void setVariableValue(String name, int value){
        if (!variables.containsKey(name)){
            throw new CodeExecuteException("Method " + this.name + " don't"
                    + " containe variable " + name);
        }

        variables.put(name, value);
    }

    public List<Instruction> getInstructions() {
        return instructions;
    }

    public void addInstruction(ICode code, Object arg){
        instructions.add(new Instruction(code, arg));
    }

    public int getCurrentInstructionIndex(){
        return instructions.size() - 1;
    }

    public Instruction getInstruction(int index){
        return instructions.get(index);
    }

    @Override
    public void print(String space) {
        out.println(space + name + "{");
        
        if (!variables.isEmpty()){
            out.println(space + "  variables:");
            for(int i: variablesIndexes.keySet()){
            	String vname = variablesIndexes.get(i);
            	out.println(String.format("%s    %2d)%s = %d", space, i, vname, 
            			variables.get(vname)));
            }
        }

        if (!instructions.isEmpty()){
            out.println(space + "  instructions:");

            for (int i = 0; i < instructions.size(); i++) {
                instructions.get(i).print(space + "    ");
            }
        }

        out.println(space + "}");
    }

    public boolean isStaticModifier() {
        return staticModifier;
    }

    public void setStaticModifier(boolean staticModifier) {
        this.staticModifier = staticModifier;
    }

    /**
     * @deprecated this method must be used only for serialization
     */
    public Map<String, Integer> getVariables() {
        return variables;
    }

    /**
     * @deprecated this method must be used only for serialization
     */
    public void setVariables(HashMap<String, Integer> variables) {
        this.variables = variables;
    }

    /**
     * @deprecated this method must be used only for serialization
     */
    public Map<Integer, String> getVariablesIndexes() {
        return variablesIndexes;
    }

    /**
     * @deprecated this method must be used only for serialization
     */
    public void setVariablesIndexes(HashMap<Integer, String> variablesIndexes) {
        this.variablesIndexes = variablesIndexes;
    }

    /**
     * @deprecated this method must be used only for serialization
     */
    public void setInstructions(ArrayList<Instruction> instructions) {
        this.instructions = instructions;
    }

}
